package com.smzd.utils.crypt;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;

public class StringCrypt64Helper {
    private static final int TABLE_LENGTH = 64;
    private static String table1 = "aFGvwKM6OP-Z0bghC3cHI!mNudDf42pEs7Ji1eqkB8Qlrjx5nzARTX*YLUoyVW9St";//

    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 1000; i++) {
            try {
                String str = packIntegers(i);
                System.out.print(i + ":\t" + str);
                Integer[] strs = unpackIntegers(str);// "o9oVVyWyV9WWS9");
                for (Object obj : strs) {
                    System.out.println("\t==>" + obj + ":" + obj.getClass().getSimpleName());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // String email = "xxxxxxxxx";
        // String enc = packStrings(email);
        // System.out.println(email);
        // System.out.println(enc);
        //
        // Integer[] strs = unpackIntegers(str);//"o9oVVyWyV9WWS9");
        // for(Object obj : strs){
        // System.out.println(obj+":"+obj.getClass().getSimpleName());
        // }
        // System.out.println();
        // String r64 = "x5xnn";

        // int pos = tablePos('F', 1);
        // System.out.println(pos);
        // int value = radix64ToInt("F", 1);
        // System.out.println(value);

        // int offset = 0;
        // String r64 = intToRadix64(-988, offset);
        // System.out.println(r64);
        // int n64 = radix64ToInt(r64, offset);
        // System.out.println(n64);

        // String r64 =
        // packStringAndIntegersWithOffset(32,1);//packStringsWithOffset(43,
        // "manager");
        // System.out.println(r64);
        // r64 = packStringsWithOffset(32,"1");
        // System.out.println(r64);
        // r64 = packIntegersWithOffset(32,1);
        // System.out.println(r64);
        // r64 = packStringAndIntegersWithOffset(32,"1");
        // System.out.println(r64);

        // Object[] values = unpackStringAndIntegers(r64);
        // for(Object obj : values){
        // System.out.println(obj.getClass().getSimpleName()+":"+obj);
        // }

        // char c1 = 't';
        // char c2 = tableProjectRight(c1, 6);
        // char t = tableTailChar(6);
        // System.out.println("tail = "+t);
        // System.out.println(c1+"-->"+c2);

        // int offset = 1;
        // String r64 = stringToRadix64("abc", offset);
        // System.out.println(r64);
        // String decoded = radix64ToString(r64, offset);
        // System.out.println(decoded);

        // int offset = 10;
        // String r64 = "0c0u2i5eZ";//stringToRadix64("abcdeft", offset);
        // System.out.println(r64);
        // String ori = radix64ToString(r64, offset);
        // System.out.println(ori);
        // int
        // i=0;
        // for(int i=0; i<64; i++){
        // System.out.println("--------offset="+i+"===============");
        // testStringAndIntegers(i);
        // String str = packStrings(i, "fsafwy3geg4");
        // System.out.println(str);
        // testIntegers(i);
        // testStrings(i);
        // }
        // wrapMd5("");
        // System.out.println(tableTail(0));
        // System.out.println(String.valueOf(tableChar(5,0)));
        // System.out.println(tableValue('G', 0));
    }

    /**
     * tableTailPos：获得tail的绝对位置。tail的相对位置永远是-1（相对于offset）。
     * 
     * @param offset
     * @return
     * @throws Exception
     */
    private static int tableTailPos(int offset) throws Exception {
        offset--;
        offset = initR64Value(offset);
        return offset;
    }

    /**
     * tableTailChar：返回在offset为基准的tail字符。tail字符定义为offset前面的一个字符。如果offset为0，
     * 则循环到尾部取字符。
     * 
     * @param offset
     * @return
     * @throws Exception
     */
    private static char tableTailChar(int offset) throws Exception {
        offset--;
        offset = initR64Value(offset);
        return table1.charAt(offset);
    }

    /**
     * tableChar：返回在offset指示的基准下，相对坐标pos指示的字符。实际上相当于密码表的（offset+pos）位置的字符
     * 
     * @param pos
     * @param offset
     * @return
     * @throws Exception
     */
    private static char tableChar(int pos, int offset) throws Exception {
        offset = initR64Value(offset);
        pos += offset;
        pos = initR64Value(pos);
        return table1.charAt(pos);
    }

    private static int initR64Value(int offset) {
        while (offset < 0) {
            offset += 65;
        }
        while (offset >= 65) {
            offset -= 65;
        }
        return offset;
    }

    /**
     * tablePos: 返回字符ch在offset指示的基准为之下的坐标，是一个相对坐标。实际上，该坐标+offset为密码表坐标。
     * 
     * @param ch
     * @param offset
     * @return
     * @throws Exception
     */
    private static int tablePos(char ch, int offset) throws Exception {
        int pos = table1.indexOf(ch);
        if (pos < 0) {
            throw new Exception("Error calling tableValue! ch=" + String.valueOf(ch));
        }
        offset = initR64Value(offset);
        pos -= offset;
        pos = initR64Value(pos);
        return pos;
    }

    /**
     * tableProjectRight：将字符相对于offset进行右向位移。位移的数量为offset。注意：位移的时候跳过了tail字符。<br>
     * 因此，右向位移是以offset为基准点、元字符的绝对位置为偏移量的字符映射。<br>
     * 注意：字符不能是tail，否则抛出异常。这样，任意字符向右位移offset是没有任何问题的。但，
     * tail右边的字符实际上由于跨越tail而位移量应该减一。
     * 
     * @param ch
     * @param offset
     * @return
     * @throws Exception
     */
    private static char tableProjectRight(char ch, int offset) throws Exception {
        offset = initR64Value(offset);
        int index = table1.indexOf(ch);
        if (index == -1 || index == (offset - 1)) {
            throw new Exception("Char BEYOUND table!char=" + ch);
        }
        if (index < offset) {
            index++;
        }
        index += offset;
        index = initR64Value(index);
        if (index == offset - 1) {
            index++;
        }
        return table1.charAt(index);
    }

    /**
     * tableProjectLeft：将字符向左位移。作为tableProjectRight的逆向操作，
     * 
     * @param ch
     * @param offset
     * @return
     * @throws Exception
     */
    private static char tableProjectLeft(char ch, int offset) throws Exception {
        offset = initR64Value(offset);
        int index = table1.indexOf(ch);// right之后的位置
        if (index == -1 || index == (offset - 1)) {
            throw new Exception("Char BEYOUND table!char=" + ch);
        }
        if (index < offset) {
            index += TABLE_LENGTH;
            index++;
        }
        int newIndex = index - offset;
        if (newIndex < offset && index >= offset) {
            newIndex--;
        }
        if (newIndex < 0) {
            newIndex += TABLE_LENGTH;
            newIndex++;
        }
        return table1.charAt(newIndex);
    }

    private static final Random RANDOM = new Random();

    // md5方法
    private static String md5(String plainText) {
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return plainText;
        }
        md.update(plainText.getBytes());
        byte b[] = md.digest();

        int i;

        StringBuffer buf = new StringBuffer("");
        for (int offset = 0; offset < b.length; offset++) {
            i = b[offset];
            if (i < 0)
                i += 256;
            if (i < 16)
                buf.append("0");
            buf.append(Integer.toString(i, 16));
        }
        // 16位的加密
        return buf.toString();
    }

    private static String intToRadix64(int number, int offset) throws Exception {
        StringBuffer sb = new StringBuffer();
        boolean minus = false;
        if (number < 0) {
            number = -number;
            minus = true;
        }
        while (number >= 64) {
            int num = number % 64;
            sb.append(tableChar(num, offset));
            offset++;
            number = number / 64;
        }
        sb.append(tableChar(number, offset));
        offset++;
        if (minus) {
            sb.append(tableTailChar(offset));
        }
        sb.reverse();
        return sb.toString();
    }

    private static int radix64ToInt(String number64, int offset) throws Exception {
        if (number64 == null || number64.trim().length() == 0) {
            throw new Exception("Empty Content!");
        }
        int sign = 1;
        offset += (number64.length() - 1);
        if (number64.charAt(0) == tableTailChar(offset)) {
            number64 = number64.substring(1);
            sign = -1;
            offset--;
        }
        if (number64.trim().length() == 0) {
            throw new Exception("Empty Content with MINUS!");
        }
        int length = number64.length();
        int value = 0;
        for (int i = 0; i < length; i++) {
            value *= 64;
            char c = number64.charAt(i);
            int v = tablePos(c, offset);
            offset--;
            if (v < 0) {
                throw new Exception("NOT a INTEGER Number!");
            }
            value += v;
        }
        return sign == 1 ? value : -value;
    }

    private static String stringToRadix64(String value, int offset) throws Exception {
        char tail = tableTailChar(offset);
        if (value == null) {
            return String.valueOf(tail);
        }
        if (value.length() == 0) {
            return tail + "" + tableChar(0, offset);
        }
        int length = value.length();
        StringBuffer sb = new StringBuffer();
        String valueLength = intToRadix64(value.length(), offset);
        sb.append(intToRadix64(valueLength.length(), offset)).append(valueLength);
        for (int i = 0; i < length; i++) {
            char ch = value.charAt(i);
            tail = tableTailChar(offset);
            if (ch == tail) {
                sb.append(tail).append(tail);
            } else {
                try {
                    ch = tableProjectRight(ch, offset);
                    sb.append(ch);
                } catch (Exception e) {
                    String code = intToRadix64(ch, offset);
                    sb.append(tail).append(intToRadix64(code.length(), offset)).append(code);
                }
            }
            offset += 5;
        }
        return sb.toString();
    }

    private static String radix64ToString(String value, int offset) throws Exception {
        char tail = tableTailChar(offset);
        if (value == null) {
            throw new Exception("NULL source!");
        }
        if (value.length() == 1 && tail == value.charAt(0)) {
            return null;
        }
        if (value.length() == 2 && value.charAt(0) == tail && value.charAt(1) == tableChar(0, offset)) {
            return "";
        }
        int length = value.length();
        int index = 0;
        StringBuffer sb = new StringBuffer();
        char ch = value.charAt(index++);
        int size = radix64ToInt(String.valueOf(ch), offset);
        String radix64 = value.substring(index, index + size);
        index += size;
        size = radix64ToInt(radix64, offset);
        while (index < length) {
            ch = value.charAt(index++);
            tail = tableTailChar(offset);
            if (ch == tail) {
                ch = value.charAt(index++);
                if (ch == tail) {
                    sb.append(tail);
                } else {
                    int len = radix64ToInt(String.valueOf(ch), offset);
                    radix64 = value.substring(index, index + len);
                    index += len;
                    int code = radix64ToInt(radix64, offset);
                    sb.append((char) code);
                }
            } else {
                ch = tableProjectLeft(ch, offset);
                sb.append(ch);
            }
            offset += 5;
        }
        if (sb.length() != size) {
            throw new Exception("Size NOT EQUAL!");
        }
        return sb.toString();
    }

    public static String packIntegers(Integer... ids) throws Exception {
        int offset = RANDOM.nextInt(64);
        return packIntegersWithOffset(offset, ids);
    }

    /**
     * packIntegersWithOffset：打包整数数组。 [offset][length]
     * 
     * @param offset
     * @param ids
     * @return
     * @throws Exception
     */
    private static String packIntegersWithOffset(int offset, Integer... ids) throws Exception {
        if (ids == null || ids.length == 0) {
            return String.valueOf(tableTailChar(offset));
        }
        StringBuffer sb = new StringBuffer();
        // 先将offset作为第一个加密使用的key，并将其转换为R64。我们约定只允许使用长度小于64的整数数组，因此长度只有一位。
        String rKey = intToRadix64(offset, 0);
        sb.append(rKey);
        sb.append(intToRadix64(ids.length, offset));
        // offset用过一次后每个数字的加密都自增一次，这样相同的数字会被加密成为不同的数字
        for (int i = 0; i < ids.length; i++) {
            offset++;
            // 每个数字的前面都有一个tail标记。注意tail是每次都变的，因为offset在变。问题：tail标记是否有必要？
            String radix64 = intToRadix64(ids[i], offset);
            sb.append(tableTailChar(offset)).append(intToRadix64(radix64.length(), offset)).append(radix64);
        }
        return sb.toString();
    }

    public static Integer[] unpackIntegers(String value) throws Exception {
        if (value == null || value.length() == 1) {
            throw new Exception("NULL source!");
        }
        String rKey = value.substring(0, 1);
        int offset = radix64ToInt(rKey, 0);
        value = value.substring(1);
        if (value.length() == 1 && tableTailChar(offset) == value.charAt(0)) {
            return null;
        }
        // 这是一个长度，即所有数据的数量
        char ch = value.charAt(0);
        // 将R64转换为10进制的长度数量
        int count = radix64ToInt(String.valueOf(ch), offset);
        // 开辟数量空间
        Integer[] results = new Integer[count];
        // index指示的是当前数字，长度已经取出来了，因此index初始值为1
        int index = 1;
        int len;
        String radix64;
        for (int i = 0; i < count; i++) {
            // 来取第一个数字，数字结构为（offset，length, value），其中offset占一个字符的长度，此处直接跳过去，不验证
            index++;
            // 每个字加密时offset都自增1
            offset++;
            // 取出长度值来
            ch = value.charAt(index++);
            // 通过offset来计算长度的值
            len = radix64ToInt(String.valueOf(ch), offset);
            // 下面根据长度的值取若干的字符
            radix64 = value.substring(index, index + len);
            // 把这段字符串变为数字
            results[i] = radix64ToInt(radix64, offset);
            // index继续往下自增
            index += len;
        }

        return results;
    }

    public static String packStrings(String... strings) throws Exception {
        int offset = RANDOM.nextInt(64);
        return packStringsWithOffset(offset, strings);
    }

    private static String packStringsWithOffset(int offset, String... strings) throws Exception {
        if (strings == null || strings.length == 0) {
            return String.valueOf(tableTailChar(offset));
        }
        StringBuffer sb = new StringBuffer();
        String rKey = intToRadix64(offset, 0);
        sb.append(rKey);
        sb.append(intToRadix64(strings.length, offset));
        offset++;
        for (int i = 0; i < strings.length; i++) {
            String radix64 = stringToRadix64(strings[i], offset);
            sb.append(intToRadix64(radix64.length(), offset)).append(radix64);
            offset++;
        }
        return sb.toString();
    }

    public static String[] unpackStrings(String value) throws Exception {
        if (value.length() == 1) {
            return null;
        }
        String rKey = value.substring(0, 1);
        int offset = radix64ToInt(rKey, 0);
        value = value.substring(1);
        char ch = value.charAt(0);
        int count = radix64ToInt(String.valueOf(ch), offset);
        String[] results = new String[count];
        int index = 1;
        int len;
        String radix64;
        offset++;
        for (int i = 0; i < count; i++) {
            ch = value.charAt(index++);
            len = radix64ToInt(String.valueOf(ch), offset);
            radix64 = value.substring(index, index + len);
            results[i] = radix64ToString(radix64, offset);
            index += len;
            offset++;
        }

        return results;
    }

    public static String packStringAndIntegers(Object... objects) throws Exception {
        int offset = RANDOM.nextInt(64);
        return packStringAndIntegersWithOffset(offset, objects);
    }

    private static String packStringAndIntegersWithOffset(int offset, Object... objects) throws Exception {
        if (objects == null || objects.length == 0) {
            return String.valueOf(tableTailChar(offset));
        }
        StringBuffer sb = new StringBuffer();
        String rKey = intToRadix64(offset, 0);
        sb.append(rKey);
        sb.append(intToRadix64(objects.length, offset));
        offset++;
        for (int i = 0; i < objects.length; i++) {
            Object object = objects[i];
            if (object instanceof String) {
                String radix64 = stringToRadix64((String) object, offset);
                sb.append(intToRadix64(radix64.length(), offset)).append(radix64);
            } else if (object instanceof Integer) {
                sb.append(tableTailChar(offset));
                String radix64 = intToRadix64((Integer) object, offset);
                sb.append(intToRadix64(radix64.length(), offset)).append(radix64);
            } else {
                throw new Exception("Only String and Integer is supported!");
            }
            offset++;
        }
        return sb.toString();
    }

    public static Object[] unpackStringAndIntegers(String value) throws Exception {
        if (value == null || value.length() == 1) {
            throw new Exception("NULL source!");
        }
        String rKey = value.substring(0, 1);
        int offset = radix64ToInt(rKey, 0);
        value = value.substring(1);
        if (value.length() == 1 && tableTailChar(offset) == value.charAt(0)) {
            return null;
        }
        char ch = value.charAt(0);
        int count = radix64ToInt(String.valueOf(ch), offset);
        offset++;
        Object[] results = new Object[count];
        int index = 1;
        int len;
        String radix64;
        for (int i = 0; i < count; i++) {
            ch = value.charAt(index++);
            char tail = tableTailChar(offset);
            if (tail == ch) {
                ch = value.charAt(index++);
                len = radix64ToInt(String.valueOf(ch), offset);
                radix64 = value.substring(index, index + len);
                results[i] = radix64ToInt(radix64, offset);
            } else {
                len = radix64ToInt(String.valueOf(ch), offset);
                radix64 = value.substring(index, index + len);
                results[i] = radix64ToString(radix64, offset);
            }
            index += len;
            offset++;
        }

        return results;
    }

    private static void testIntegers(int offset) throws Exception {
        String str64 = packIntegersWithOffset(offset, 1, 2, 3333, 4433, -99, -988);
        System.out.println(str64);
        Integer[] results = unpackIntegers(str64);
        for (int i = 0; i < results.length; i++) {
            System.out.println(results[i]);
        }
        System.out.println("-------------");
    }

    private static void testStrings(int offset) throws Exception {
        String str64 = packStringsWithOffset(offset, "abc", "你好", "啊s飞ss");
        System.out.println(str64);
        Object[] results = unpackStrings(str64);
        for (int i = 0; i < results.length; i++) {
            System.out.println(results[i]);
        }
    }

    private static void testStringAndIntegers(int offset) throws Exception {
        String str64 = packStringAndIntegers(offset, "abc", 1234, "你好", "啊s飞ss", 33322);
        System.out.println(str64);
        Object[] results = unpackStringAndIntegers(str64);
        for (int i = 0; i < results.length; i++) {
            System.out.println(results[i]);
        }
    }

    private static String wrapMd5(String text) {
        int id = RANDOM.nextInt(64);
        System.out.println(id);
        return null;
    }

    private static void test(int offset) throws Exception {
        String number64 = intToRadix64(-9985662, offset);
        byte[] bytes = number64.getBytes();
        System.out.println("begin");
        for (int i = 0; i < bytes.length; i++) {
            System.out.println(bytes[i]);
        }
        System.out.println("finished");
        System.out.println(number64);
        try {
            int number = radix64ToInt(number64, offset);
            System.out.println(number);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
